﻿/*
 * 参考: https://www.grpc.io/docs/
 */

#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include <cstdlib>
#include <cstdio>

#include <grpcpp/grpcpp.h>

 // xxx.grpc.pb.h文件通过protoc.exe和grpc_cpp_plugin.exe编译得到
 // 然后手动复制到项目文件夹下,并手动添加到解决方案!
#include "hello.grpc.pb.h"
#include "vno.grpc.pb.h"

// grpc提供的类型(供客户端使用)
using grpc::Channel;
using grpc::ClientContext;
using grpc::Status;

// 在hello.grppc.pb.h中定义(与hello.proto定义一致)
using hello::HelloRequest;
using hello::HelloReply;
using hello::GreetService;

// 在vno.grppc.pb.h中定义(与vno.proto定义一致)
using vno::VnoRequest;
using vno::VnoReply;
using vno::VnoService;
using vno::Graph;
using vno::Edge;
using vno::Vertex;
using vno::WindGate;

#define BOOL_2_STR(b) b?"有":"无"

// 定义客户端
class GrpcClient {
public:
	// 构造函数
	// 参数:
	//	channel -- 代表一个客户端与服务器之间的通道(没关闭之前,一直可以通过stub与服务器进行通信,称之为"长连接")
	//	stub    -- 代表一个service对象的通信桩(与服务器通信就靠它,可以随时创建,用完就关闭,称之为"短连接")
	//             stub可以定义多个,根据实际需求而定.
	GrpcClient(std::shared_ptr<Channel> channel)
		// 初始化GreetService通信桩
		: greet_stub(GreetService::NewStub(channel)),
		// 初始化VnoService通信桩
		vno_stub(VnoService::NewStub(channel)) {
		// do something
		// ...
	}

	// 向服务器请求SayHello调用(参见hello.proto/GreetService/SayHello定义)
	// 参数:
	//	user -- 用户名
	// 返回值: 消息字符串
	// Assembles the client's payload, sends it and presents the response back
	// from the server.
	std::string sayHello(const std::string& user) {
		// 1) 输入: 准备要发送给服务器的数据
		// Data we are sending to the server.
		HelloRequest request;
		request.set_name(user);

		// 2) 输出: 服务器返回的结果
		// Container for the data we expect from the server.
		HelloReply reply;

		// 3) 客户端上下文
		// Context for the client. It could be used to convey extra information to
		// the server and/or tweak certain RPC behaviors.
		ClientContext context;

		// rpc调用过程(通过通信桩对象来完成!)
		// The actual RPC.
		Status status = greet_stub->SayHello(&context, request, &reply);

		// Act upon its status.
		if (status.ok()) {
			return reply.message();
		}
		else {
			std::cout << status.error_code() << ": " << status.error_message()
				<< std::endl;
			return "RPC failed";
		}
	}

	// 参考: protobuf repeated类型的使用(http://www.cppblog.com/API/archive/2014/12/09/209070.aspx)
	// 一个最简单的θ型网络
	void buildGraph(Graph* graph) {
		// 增加节点
		vno::Vertex* v1 = graph->add_vertex();
		vno::Vertex* v2 = graph->add_vertex();
		vno::Vertex* v3 = graph->add_vertex();
		vno::Vertex* v4 = graph->add_vertex();
		vno::Vertex* v5 = graph->add_vertex();
		vno::Vertex* v6 = graph->add_vertex();
		// 设置节点id编号
		v1->set_id("v1");
		v2->set_id("v2");
		v3->set_id("v3");
		v4->set_id("v4");
		v5->set_id("v5");
		v6->set_id("v6");

		// 增加分支
		vno::Edge* e1 = graph->add_edge();
		vno::Edge* e2 = graph->add_edge();
		vno::Edge* e3 = graph->add_edge();
		vno::Edge* e4 = graph->add_edge();
		vno::Edge* e5 = graph->add_edge();
		vno::Edge* e6 = graph->add_edge();
		vno::Edge* e7 = graph->add_edge();
		// 设置分支id编号
		e1->set_id("e1");
		// 设置摩擦风阻
		e1->set_rfirc(0.375);
		// 设置始末节点(需要用指针!)
		e1->set_allocated_from(v1);
		e1->set_allocated_to(v2);

		e2->set_id("e2");
		e2->set_rfirc(1.5);
		e2->set_allocated_from(v2);
		e2->set_allocated_to(v3);

		e3->set_id("e3");
		e3->set_rfirc(2.0);
		e3->set_allocated_from(v2);
		e3->set_allocated_to(v4);

		e4->set_id("e4");
		e4->set_rfirc(4.6875);
		e4->set_allocated_from(v3);
		e4->set_allocated_to(v5);

		e5->set_id("e5");
		e5->set_rfirc(12.5);
		e5->set_allocated_from(v3);
		e5->set_allocated_to(v4);

		e6->set_id("e6");
		e6->set_rfirc(1.7361);
		e6->set_allocated_from(v4);
		e6->set_allocated_to(v5);

		e7->set_id("e7");
		e7->set_rfirc(0.5);
		e7->set_allocated_from(v5);
		e7->set_allocated_to(v6);

		// 增加风机
		vno::FanPower* fan = graph->add_fan();
		// 设置风机id编号
		fan->set_id("f1");
		// 设置风机特性曲线参数a0,a1,a2
		fan->set_a0(1046.3);
		fan->set_a1(5.0);
		fan->set_a2(-0.85);
		// 设置风机绑定的巷道
		fan->set_allocated_edge(e7);
		fan->mutable_edge();
	}

	// 要对指针对象进行修改,必须要使用mutable_xxx()函数获取指针,否则修改无效!!!
	void releaseRequest(VnoRequest& request) {
		if (request.has_graph()) {
			// 手动释放edge指针(无效方法!!!)
			//auto fans = request.graph().fan();
			//for (int i = 0; i < fans.size(); i++) {
			//	fans.at(i).release_edge();				
			//}

			// 手动释放edge指针(有效方法!!!)
			//	at(i) -> mutable_at(i)
			// 注意:graph()  fan()  at(i)  返回的都是const&
			//	    mutable_graph()   mutable_fan()   mutable_at(i)  返回的都是指针
			vno::Graph* graph = request.mutable_graph();
			auto fan_size = graph->fan_size();
			for (int i = 0; i < fan_size; i++) {
				// 对于嵌套的对象,要通过mutable_xxx来修改数据!!!
				graph->mutable_fan(i)->release_edge();
			}
			auto gate_size = graph->gate_size();
			for (int i = 0; i < gate_size; i++) {
				// 对于嵌套的对象,要通过mutable_xxx来修改数据!!!
				graph->mutable_gate(i)->release_edge();
			}
			auto edge_size = graph->edge_size();
			for (int i = 0; i < edge_size; i++) {
				// 对于嵌套的对象,要通过mutable_xxx来修改数据!!!
				graph->mutable_edge(i)->release_from();
				graph->mutable_edge(i)->release_to();
			}
		}
	}

	void printRequest(VnoRequest& request) {
		// graph() fan() edge() at(i) 这些都是const& 方法
			// 在语义上也说明,数据只可读取,不能通过它们去修改!!!
		auto fans = request.graph().fan();
		for (int i = 0; i < fans.size(); i++) {
			// 手动释放edge指针
			std::cout << "风机:" << fans.at(i).has_edge() << std::endl;
		}
		auto edges = request.graph().edge();
		for (int i = 0; i < edges.size(); i++) {
			std::cout << "始节点:" << edges.at(i).has_from() << "末节点：" << edges.at(i).has_to() << std::endl;
		}
		auto vertexs = request.graph().vertex();
		for (int i = 0; i < vertexs.size(); i++) {
			std::cout << "节点:" << vertexs.at(i).id() << std::endl;
		}
	}

	// 向服务器请求Run调用(参见vno.proto/VnoService/Run定义)
	bool runVno(int maxIterNum, double precision) {
		// 1) 输入: 准备要发送给服务器的数据
		// 按照vno.proto的定义,需要传入一个VnoRequest对象
		// 而VnoRequest对象需要一个Graph对象
		VnoRequest request;
		// 设置最大迭代次数
		request.set_maxnumber(maxIterNum);
		// 设置期望精度
		request.set_expectedprecision(precision);

		/*
			搜索关键词: protobuf set_allocated segment fault
			https://stackoverflow.com/questions/53366505/segmentation-fault-using-protobuf
			https://stackoverflow.com/questions/43177492/freeing-protobuf-generated-class-causes-a-a-segment-fault
			https://stackoverflow.com/questions/53648009/google-protobuf-mutable-foo-or-set-allocated-foo
			https://stackoverflow.com/questions/39714796/protobuf-serializetoarray-rasie-segmentation-fault-at-internalserializewithcache
			https://stackoverflow.com/questions/33960999/protobuf-will-set-allocated-delete-the-allocated-object

			protobuf关于嵌套对象的内存错误问题:
			*用法:
				1) set_allocated_xxx(pointer)  外部new一个对象(要求在堆上分配,而不是栈)
					fan->set_allocated_edge(e7)
				2) mutable_edge()  第一次调用, 内部自动new一个对象
					vno::Edge* e = fan->mutable_edge();  // 这是一个全新的对象
				在析构的时候,protobuf内部会自动的delete指针(也就是说对象的生命所有权已经转移给protobuf了)
				参考: https://blog.csdn.net/taoqilin/article/details/81208101

			*问题:
				某些情况下会导致指针被删除多次!!!

			*分析:
				析构顺序: VnoRequest -> Graph -> Fan数组 -> Edge -> Vertex
				VnoRequest内部有1个Graph指针
				Graph内部有1个Fan*数组
				Fan内部有1个Edge指针
				Edge内部有2个Vertex指针

				当Fan析构完之后,继续析构Edge和Vertex数组,就回出现个别Edge和Vertex被delete两次的情况

			*解决方法:
				在最后析构的时候手动释放所有权!
		*/

		// 设置graph对象(要使用堆分配的指针!)
		/*vno::Graph* graph = new vno::Graph();
		request.set_allocated_graph(graph);
		buildGraph(graph);*/

		// 或者mutable_graph()内部自动new一个Graph对象
		buildGraph(request.mutable_graph());

		// 2) 输出: 服务器返回的结果
		VnoReply reply;

		// 3) 客户端上下文
		ClientContext context;

		// rpc调用过程(通过通信桩对象来完成!)
		// The actual RPC.
		Status status = vno_stub->Run(&context, request, &reply);

		// 手动释放request中的部分指针,解决指针被多次删除的问题!
		releaseRequest(request);
		printRequest(request);

		// Act upon its status.
		if (status.ok()) {
			return reply.success();
		}
		else {
			std::cout << status.error_code() << ": " << status.error_message()
				<< std::endl;
			return false;
		}
	}

private:
	// GreetService通信桩
	std::unique_ptr<GreetService::Stub> greet_stub;
	// VnoService通信桩
	std::unique_ptr<VnoService::Stub> vno_stub;
};

void pause() {
	std::system("pause");
}

int main(int argc, char** argv) {
	// rpc服务器ip(根据实际情况给定正确的ip,比如局域网:192.168.1.243,公网ip:55.44.33.22)
	std::string host = "localhost";
	// 端口(要与服务器的端口一致!!!)
	int port = 50051;
	// 服务器完整地址
	std::string server_address(host + ":" + std::to_string(port));

	// Instantiate the client. It requires a channel, out of which the actual RPCs
	// are created. This channel models a connection to an endpoint (in this case,
	// localhost at port 50051). We indicate that the channel isn't authenticated
	// (use of InsecureChannelCredentials()).

	// 1) 使用host、port创建一个我们自己封装的客户端对象
	// 注:InsecureChannelCredentials表示客户端与服务器之间的通信无权限验证,无加密(No SSL)
	GrpcClient client(grpc::CreateChannel(server_address, grpc::InsecureChannelCredentials()));

	// 2) 测试
	// a. 测试SayHello调用
	std::string reply = client.sayHello("world");
	std::cout << "Greeter received: " << reply << std::endl;
	// b. 测试vno调用
	if (client.runVno(1000, 0.01)) {
		std::cout << "网络解算成功" << std::endl;
	}
	else {
		std::cout << "网络解算失败" << std::endl;
	}

	// 暂停(回车后退出)
	pause();

	return 0;
}
